/*
 * Package il.ac.biu.cs.grossmm.test.SimpleProtocol
 * File SingleClient.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.foo.protocol;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.CharBuffer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

public abstract class SingleClient implements Runnable {
	volatile Socket socket;
	private BufferedReader in;
	private BufferedOutputStream out;
	
	Thread thread;
	
	Response response;
	Lock respLock = new ReentrantLock();
	Condition respCond = respLock.newCondition();
	
	Lock reqLock = new ReentrantLock();
	
	
	
	public SingleClient()
	{
		NotifyRequest.understand();
	}

	public  void connectTo(String host, int port) throws UnknownHostException, IOException {
		if( socket!=null && socket.isConnected()  )
			throw new IOException("Already connected");
		
		socket = new Socket(host, port);
		
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		out = new BufferedOutputStream(socket.getOutputStream());
		
		thread = new Thread(this, "Client Input");
		thread.start();
	}
	
	public void disconnect() throws IOException {
		if( socket == null || thread == null  )
			throw new IOException("Not connected");
		
		thread.interrupt();
		socket.close();
	}
	
	public Response sendRequest(Request request) throws IOException, InterruptedException {
		send(request);
		
		Response result;
		
		respLock.lock();
		while( response == null )
			respCond.await();
		result = response;
		response = null;
		respLock.unlock();
		
		return result;
	}
	
	public void run()
	{
		char[] buf = new char[512];
		
		StringBuffer sb = new StringBuffer();

		while( !thread.isInterrupted() && socket != null ) {
			int n;
			try {
				n = in.read(buf);
			} catch( SocketException se) {
				n=-1;
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}
			
			if( n == -1 ) {
				System.out.println("Disconnected");
				return;
			}
			
			int start = 0;
			
			for( int i=0; i<n; i++ ) {
				if( buf[i] == Message.MSG_DELIMETER ) {
					if( i-start == 1 )
						continue;
					
					try {
						sb.append(buf, start, i-start);
					} catch( Exception e) {
						throw new RuntimeException(e);
					}
					
					process(sb.toString());

					sb.setLength(0);
					start = i;
				}
			}
			
			// add the rest of the buffer (an incomplete message)
			// to the sb, so that we can append the rest of the
			// message later.
			sb.append(buf, start, n-start-1);
		}
	}

	private void process(String string)
	{
		Message msg = Message.parse(string);
		if( msg instanceof Request ) {
			// request
			Request request = (Request) msg;
			Response r = requestReceived(request);
			
			
			String s = r.toString();
			
			reqLock.lock();
			try {
				try {
					out.write(s.getBytes("utf-8"));
				} catch (Exception e) {
					// TODO: close connection
					e.printStackTrace();
				}
			} finally {
				reqLock.unlock();
			}
		} else {
			// response
			
			respLock.lock();
			response = (Response) msg;
			respCond.signal();
			respLock.unlock();
		}
	}
	
	private void send(Request request) throws IOException {
		CharSequence strReq = request.serialize();

		reqLock.lock();
		try {
			out.write(strReq.toString().getBytes("utf-8"));
		} catch( Exception e ) {
			throw new IOException("Could not send request: "+e.getMessage() );
		} finally {
			reqLock.unlock();
		}
		
		out.flush();
	}
	
	
	
	protected abstract Response requestReceived(Request request);
}
